﻿HmJS.$Import('core.element');

/*
---
version: 1.3.1-0
description: VisibilityWatcher is an event or poll based DOM element visibility detection class.
license: MIT-style
download: http://mootools.net/forge/p/visibilitywatcher
source: http://github.com/fcartegnie/VisibilityWatcher

authors:
- Francois Cartegnie

requires:
- core/1.2.3: '*'

provides: [VisibilityWatcher]
...
*/

/*
How to Use
----------
	#JS
	new VisibilityWatcher($('target'),
	{
  		'enteredscreen': function(){
				alert('Element is on screen');
		}
	});

Events will happen when the element is scrolled in or out of the viewport, and depends on scroll events.
If you need to detect an element when there's no scroll event (no user action, animated by scripts) you can use the `poll` method.

	#JS
	new VisibilityWatcher($$('.watched'),
	{
  		'enteredscreen': function(el){
				alert('Element ' + el.id + 'is on screen');
		}
	},
	{ 'method': 'poll' }
	);

You can also discard 'fast scrolling' events by only triggering the event when the element stays in the same state for more than the specified `delay` value.
The `delta_px` allows extending the detection area. Can be used to trigger the event when the element is close to enter screen.

	#JS
	new VisibilityWatcher($$('.watched'),
	{
  		'enteredscreen': function(el){
				alert('Element ' + el.id + 'is on screen');
		}
	},
	{
		'delay': 2000,  // Only trigger event if the element is on screen for more than 2 seconds 
		'delta_px': 300  // Assume element is on screen when it enters a 300px range around viewport 
	}
	);

You can also detect element's position relatively to the viewport.

	#JS
	$('target').store('visibilitywatcher',
		new VisibilityWatcher($('target'),
		{
  			'updatedvisibilitystatus': function(){
  					var viswatcher = $('target').retrieve('visibilitywatcher');
					alert('we are ' + viswatcher.getVisibility()['x'] + ' element on x-axis and ' + viswatcher.getVisibility()['y'] + ' on y-axis');
			}
		})
	);

Return value `on` of `getVisibility()` has the same meaning as a fired `enteredscreen` event.
Return values `before` and `after` of `getVisibility()` have the same meaning as a fired `leftscreen` event.
*/

HmJS.register('util.dom.VisibilityWatcher', function ($ns) {

	return new Class({

		Implements: [Options, Events],

		options: {
			poll_interval: 2000,
			method: 'event', /* poll or scroll event based */
			delay: 0, /* Delay before considering the event as stable */
			delta_px: 0, /* Extend the detection area by delta_px pixels */
			event_source: window /* scrollable element */
		},

		initialize: function (el, events, options) {
			this.targetElements = new Array();
			this.setOptions(options);
			this.addEvents(events);
			this.add(el);
			this.visibilityChangedCheck();
			this.startWatching();
		},

		getVisibility: function (targetElement) {
			if (targetElement == 'undefined') {
				targetElement = this.targetElements[0].element;
			}

			var elementPosition = targetElement.getPosition();
			var elementSize = targetElement.getSize();
			var returned_array = new Array();

			['x', 'y'].each(function (el, index) {
				if (document.getScroll()[el] > (elementPosition[el] + elementSize[el] + this.options.delta_px)) {
					returned_array[el] = 'after';
				} else if ((document.getScroll()[el] + window.getSize()[el]) > (elementPosition[el] - this.options.delta_px)) {
					returned_array[el] = 'on';
				} else {
					returned_array[el] = 'before';
				}
			}, this);
			return returned_array;
		},

		startWatching: function () {
			if (this.options.method == 'poll') {
				this.interval_id = this.visibilityChangedCheck.periodical(this.options.poll_interval, this);
			} else {
				document.id(this.options.event_source).addEvent('scroll', this.visibilityChangedCheck.bind(this));
			}
			return this;
		},

		stopWatching: function () {
			if (this.options.method == 'poll') {
				if (this.interval_id) { clearInterval(this.interval_id); }
			} else {
				document.id(this.options.event_source).removeEvent('scroll', this.visibilityChangedCheck.bind(this));
				if (this.gratuitouscheck_id) {
					clearTimeout(this.gratuitouscheck_id);
				}
			}
			return this;
		},

		add: function (targetElement) {
			Array.from(targetElement).each(function (el, index) {
				this.targetElements.push({ 'element': document.id(el), 'last_state': new Array() });
			} .bind(this));
			return this;
		},

		remove: function (targetElement) {
			targetElement = document.id(targetElement);
			this.targetElements = this.targetElements.filter(function (el) {
				return (targetElement != el['element']);
			});
			return this;
		},

		prepareAndFireEvent: function (eventName, element) {
			this.fireEvent(eventName, element);
		},

		visibilityChangedCheck: function () {
			var currentTime = Date.now();
			this.targetElements.each(function (targetElement, index) {
				var cur_state = this.getVisibility(targetElement.element);
				if (!['x', 'y'].every(function (axis, index) { return (cur_state[axis] == targetElement.last_state[axis]); }, this)) {
					if (!targetElement.last_state['started']) {
						targetElement.last_state['started'] = currentTime;
					}
					if ((currentTime - targetElement.last_state['started']) >= this.options.delay) {
						targetElement.last_state = cur_state;
						if (['x', 'y'].every(function (axis, index) { return (cur_state[axis] == 'on'); })) {
							this.prepareAndFireEvent('enteredscreen', targetElement.element);
						} else {
							this.prepareAndFireEvent('leftscreen', targetElement.element);
						}
					} else {
						if (this.options.delay > 0 && this.options.method == 'event') {	/*Force an additional check if events have stopped*/
							if (this.gratuitouscheck_id) {
								clearTimeout(this.gratuitouscheck_id);
							}
							this.gratuitouscheck_id = this.visibilityChangedCheck.delay(this.options.delay + 1, this);
						}
					}
				} else {
					targetElement.last_state.erase('started');
				}
			} .bind(this));

			this.prepareAndFireEvent('updatedvisibilitystatus');
			return this;
		}

	});

});